package client;

import java.net.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
import java.awt.Color;
import java.io.*;

import protocol.ClientProtocol;
import protocol.ServerProtocol;
import spel.Bord;
import spel.Knikker;
import spel.Zet;

/**
 * Client. Een Thread-klasse voor het onderhouden van een Socket-verbinding met
 * een Server. De Thread leest berichten uit de socket en gebruikt deze om een bord up to date te houden en de flow van het spel te tonen
 * @author Tim en Stef
 */
public class Client extends Observable implements Runnable {

	private String clientName;
	private Socket sock;
	private BufferedReader in;
	private BufferedWriter out;
	private Bord b;
	private String nextPlayerName;
	private Map<String, Knikker> spelers;
	public static final Color[] colors = { Color.RED, Color.BLUE, Color.GREEN,
			Color.MAGENTA };
	private ai.Strategie strategie;

	/**
	 * Construeert een Client-object en probeert een socketverbinding met een
	 * Server op te starten.
	 */
	public Client(String name, InetAddress host, int port) throws IOException {
		this.clientName = name;
		sock = new Socket(host, port);
		in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
		out = new BufferedWriter(new OutputStreamWriter(sock.getOutputStream()));
		commandJoin();
		b = new Bord();
		strategie = new ai.NextLeegStrategie();
	}
	/**
	 * Retourneerd het Bord van de client.
	 * Dit bord is niet read only en zal dus bij verandering ook een verandering in de spelstaat geven die deze client ziet.
	 * @return Het bijgehouden bord.
	 */
	public Bord getBord(){
		return b;
	}
	/**
	 * De speler die op dit moment aan de beurt is
	 * @return de huidige speler
	 */
	public Knikker getCurrentKnikker(){
		return spelers.get(nextPlayerName);
	}
	/**
	 * Leest berichten uit de socketverbinding. Elk berichtje wordt gestuurd
	 * naar de MessageUI van deze Client. Als er iets fout gaat bij het lezen
	 * van berichten, sluit deze methode de socketverbinding.
	 */
	public void run() {
		while (!sock.isClosed()) {
			String moi = null;
			try {
				moi = in.readLine();
			} catch (IOException e) {
				shutdown();
			}
			if (moi != null) {
				processMessage(moi);
			}else{
				this.shutdown();
			}

		}
	}
	
	private void processMessage(String s) {
		String[] tokens = s.split("" + ClientProtocol.DELIM);
		if (tokens[0].equals(ServerProtocol.ACCEPT)) {
			addMessage("Server Joined");
		} else if (tokens[0].equals(ServerProtocol.WAIT)) {
			addMessage("Waiting for other players to join game");
		} else if (tokens[0].equals(ServerProtocol.START_GAME)
				&& tokens.length >= 2) {
			addMessage("Starting new game, Players:");
			String players = "";
			spelers = new HashMap<String, Knikker>();
			for (int i = 1; i < tokens.length; i++) {
				players = players + tokens[i] + ", ";
				this.spelers.put(tokens[i],
						new Knikker(colors[i - 1], 20, null));
			}
			b = new Bord();
			addMessage(players);
		} else if (tokens[0].equals(ServerProtocol.CHAT) && tokens.length == 2) {
			addMessage(tokens[1]);
		} else if (tokens[0].equals(ServerProtocol.GAME_OVER)) {
			if (tokens.length == 1) {
				addMessage("Game over, no winners");
			} else {
				addMessage("Game over, Winners:");
				String players = "";
				for (int i = 1; i < tokens.length; i++) {
					players = players + tokens[i] + ", ";
				}
				addMessage(players);
			}
		} else if (tokens[0].equals(ServerProtocol.NEXT_MOVE)
				&& tokens.length == 2) {
			nextPlayerName = tokens[1];
			if (nextPlayerName.equals(clientName)) {
				addMessage("Doing move");
				Zet z = strategie.getMove(b,spelers.get(clientName));
				String richtingstring;
				if (z.richting == Zet.Richting.Rechts) {
					richtingstring = "+";
				} else {
					richtingstring = "-";
				}
				this.sendCommand(ClientProtocol.DO_MOVE, new String[] {
						"" + z.x, "" + z.y, "" + z.index, richtingstring });
			} else
				addMessage("Next move: " + nextPlayerName);
		} else if (tokens[0].equals(ServerProtocol.MOVE) && tokens.length > 2) {
			Zet z = new Zet(null, Integer.parseInt(tokens[1]),
					Integer.parseInt(tokens[2]), Integer.parseInt(tokens[3]),
					(tokens[4].equals("+") ? Zet.Richting.Rechts
							: Zet.Richting.Links));
			b.PlaatsKnikker(z.x, z.y, spelers.get(nextPlayerName));
			if(b.isWinnaar().size()==0)
			b.DraaiSubBord(z.index, z.richting);
			this.setChanged();
			notifyObservers(b);
		}else if(tokens[0].equals(ServerProtocol.ERROR)){
			addMessage("ERROR"+tokens[1]+": "+tokens[2]);
		}
	}

	/**
	 * Zorgt ervoor dat de client bij het versturen van een zet naar de server deze strategie om een zet vraagt
	 * @param s de strategie om te gebruikten
	 */
	public void setStrategie(ai.Strategie s){
		strategie = s;
	}
	/** Stuurt een bericht over de socketverbinding naar de ClientHandler. */
	public void sendMessage(String msg) {
		try {
			out.write(msg + "\n");
			out.flush();
		} catch (IOException e) {
			shutdown();
		}

	}
	/**
	 * Stuurt een chatbericht naar de server
	 * @param text De text om te versturen
	 */
	public void sendChat(String text){
		sendCommand(ClientProtocol.CHAT,new String[]{text});
	}
	public void sendCommand(String command, String[] args) {
		String message = command;
		if (args != null)
			for (String s : args) {
				message = message + ClientProtocol.DELIM + s;
			}
		sendMessage(message);
	}
	/**
	 * Stuurt een join commando naar de server
	 */
	public void commandJoin() {
		sendCommand(ClientProtocol.JOIN, new String[] { clientName });
	}

	/**
	 * Verzoekt de server om een spel te speler
	 * @param players het aantal spelers om mee te spelen
	 */
	public void requestGame(int players) {
		sendCommand(ClientProtocol.REQUEST, new String[] { "" + players });
	}

	/** Sluit de socketverbinding van deze client. */
	public void shutdown() {
		if(!sock.isClosed())
		try {
			
			sock.close();
			addMessage("Closing socket connection...");
		} catch (IOException e) {
		}

	}

	/** Levert de naam van de gebruiker van deze Client. */
	public String getClientName() {
		return clientName;
	}
	/**
	 * stuurt een bericht naar alle observers
	 * @param s Het bericht om te sturen
	 */
	private void addMessage(String s) {
		this.setChanged();
		this.notifyObservers(s);
	}

} // end of class Client
